from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory, jsonify, send_file, abort, Response
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import secure_filename
from flask_migrate import Migrate
import os
from datetime import datetime, timedelta, UTC
from config import Config
from sqlalchemy import or_, and_, func, desc
from models import db, Category, Book, User, Post, Comment, Circle, Notification, ReadingProgress, Bookmark, UserActivity, BookComment, post_likes, post_favorites
import csv
import io
from functools import wraps
from PIL import Image, ImageDraw
import PyPDF2

# 为避免fitz模块导入错误警告，添加类型注释
# pyright: reportMissingImports=false

app = Flask(__name__)
app.config.from_object(Config)

# 添加文件上传配置
UPLOAD_FOLDER = 'uploads'
AVATAR_FOLDER = 'static/avatars'
COVER_FOLDER = 'static/covers'
ALLOWED_EXTENSIONS = {'pdf', 'epub', 'mobi', 'txt', 'png', 'jpg', 'jpeg', 'gif'}

app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['AVATAR_FOLDER'] = AVATAR_FOLDER
app.config['COVER_FOLDER'] = COVER_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024  # 500MB

# 检查文件扩展名是否被允许
def allowed_file(filename, file_type='book'):
    if '.' not in filename:
        return False
    ext = filename.rsplit('.', 1)[1].lower()
    if file_type == 'book':
        return ext in {'pdf', 'epub', 'mobi', 'txt'}
    elif file_type == 'avatar' or file_type == 'cover':
        return ext in {'png', 'jpg', 'jpeg', 'gif'}
    return False

# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['AVATAR_FOLDER'], exist_ok=True)
os.makedirs(app.config['COVER_FOLDER'], exist_ok=True)

# 初始化数据库
db.init_app(app)

# 初始化迁移
migrate = Migrate(app, db)

# 初始化登录管理器
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

# 管理员权限装饰器
def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not current_user.is_admin:
            abort(403)
        return f(*args, **kwargs)
    return decorated_function

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

# 记录用户活动的函数
def record_activity(user_id, activity_type, target_id=None):
    activity = UserActivity(
        user_id=user_id,
        activity_type=activity_type,
        target_id=target_id
    )
    db.session.add(activity)
    db.session.commit()

# 从PDF文件提取首页作为封面
def extract_pdf_cover(pdf_path, output_path):
    """
    从PDF文件提取首页作为封面图片
    
    参数:
        pdf_path: PDF文件路径
        output_path: 封面图片保存路径
        
    返回:
        bool: 是否成功提取封面
    """
    try:
        # 先尝试使用PyMuPDF (fitz)提取封面，它能提供更高质量的渲染
        import fitz  # type: ignore # PyMuPDF
        # 打开PDF文件
        doc = fitz.open(pdf_path)
        if len(doc) > 0:
            # 获取第一页
            first_page = doc.load_page(0)
            # 将第一页渲染为高分辨率图像
            # 使用更高的缩放因子提高质量
            zoom_factor = 4  # 提高分辨率
            mat = fitz.Matrix(zoom_factor, zoom_factor)
            pix = first_page.get_pixmap(matrix=mat)
            # 保存为图像文件
            pix.save(output_path)
            
            # 调整图像大小为标准封面尺寸
            try:
                with Image.open(output_path) as img:
                    # 裁剪为合适的宽高比 (2:3)
                    width, height = img.size
                    target_ratio = 2/3  # 宽:高 = 2:3
                    current_ratio = width/height
                    
                    if current_ratio > target_ratio:
                        # 图像太宽，裁剪宽度
                        new_width = int(height * target_ratio)
                        left = (width - new_width) // 2
                        img = img.crop((left, 0, left + new_width, height))
                    elif current_ratio < target_ratio:
                        # 图像太高，裁剪高度
                        new_height = int(width / target_ratio)
                        top = (height - new_height) // 2
                        img = img.crop((0, top, width, top + new_height))
                    
                    # 调整大小到标准尺寸
                    img = img.resize((300, 450), Image.LANCZOS)
                    img.save(output_path, quality=90)
            except Exception as e:
                print(f"调整PDF封面大小失败: {e}")
            
            doc.close()
            return True
    except ImportError:
        # 如果没有安装PyMuPDF，回退到PyPDF2
        print("PyMuPDF未安装，使用PyPDF2创建简单封面")
    except Exception as e:
        print(f"使用PyMuPDF提取PDF封面失败: {e}")
    
    # 如果PyMuPDF失败或未安装，使用PyPDF2创建一个基本封面
    try:
        with open(pdf_path, 'rb') as file:
            reader = PyPDF2.PdfReader(file)
            if len(reader.pages) > 0:
                # 创建一个标准尺寸的封面图像
                img = Image.new('RGB', (300, 450), color=(245, 245, 245))
                draw = ImageDraw.Draw(img)
                
                # 添加书脊和边框
                draw.rectangle((30, 30, 270, 420), outline=(200, 200, 200), width=2)
                draw.rectangle((110, 30, 140, 420), fill=(240, 240, 240))  # 书脊
                
                # 尝试获取PDF标题和作者
                title = None
                author = None
                if reader.metadata:
                    if hasattr(reader.metadata, 'title') and reader.metadata.title:
                        title = reader.metadata.title
                    if hasattr(reader.metadata, 'author') and reader.metadata.author:
                        author = reader.metadata.author
                
                # 如果无法获取标题，使用文件名
                if not title:
                    title = os.path.basename(pdf_path).split('.')[0]
                
                # 添加标题
                # 根据标题长度调整字体大小
                try:
                    from PIL import ImageFont
                    font_size = 24
                    title_wrapped = []
                    words = title.split()
                    line = ""
                    
                    # 简单的文本换行逻辑
                    for word in words:
                        test_line = line + " " + word if line else word
                        if len(test_line) <= 20:  # 大约每行20个字符
                            line = test_line
                        else:
                            title_wrapped.append(line)
                            line = word
                    
                    if line:
                        title_wrapped.append(line)
                    
                    # 如果标题太长，减小字体
                    if len(title_wrapped) > 3:
                        font_size = 18
                    
                    try:
                        # 尝试加载字体，失败时使用默认字体
                        font = ImageFont.load_default()
                        
                        # 绘制标题
                        y_pos = 150
                        for line in title_wrapped:
                            text_width = font.getbbox(line)[2]
                            x_pos = (300 - text_width) // 2
                            draw.text((x_pos, y_pos), line, fill=(50, 50, 50), font=font)
                            y_pos += font_size + 5
                        
                        # 如果有作者信息，添加作者
                        if author:
                            author_y = 350
                            author_text = f"作者: {author}"
                            if len(author_text) > 25:
                                author_text = author_text[:22] + "..."
                            draw.text((30, author_y), author_text, fill=(100, 100, 100), font=font)
                    except Exception as e:
                        print(f"绘制文本失败: {e}")
                        # 备用方案：简单添加文本
                        draw.text((50, 150), title[:20], fill=(50, 50, 50))
                        if len(title) > 20:
                            draw.text((50, 180), title[20:40], fill=(50, 50, 50))
                except ImportError:
                    # 如果无法导入ImageFont，使用简单绘制
                    draw.text((50, 150), title[:20], fill=(50, 50, 50))
                    if len(title) > 20:
                        draw.text((50, 180), title[20:40], fill=(50, 50, 50))
                
                # 绘制PDF图标
                draw.rectangle((120, 180, 180, 240), fill=(200, 200, 200))
                draw.rectangle((130, 190, 170, 230), fill=(245, 245, 245))
                draw.line((130, 200, 170, 200), fill=(180, 180, 180), width=1)
                draw.line((130, 210, 170, 210), fill=(180, 180, 180), width=1)
                draw.line((130, 220, 170, 220), fill=(180, 180, 180), width=1)
                
                # 保存生成的封面
                img.save(output_path, quality=90)
                return True
    except Exception as e:
        print(f"创建PDF封面失败: {e}")
    
    return False

# 为电子书创建默认封面
def create_default_cover(title, author, output_path):
    """
    为电子书创建一个默认封面
    
    参数:
        title: 书名
        author: 作者名
        output_path: 封面图片保存路径
        
    返回:
        bool: 是否成功创建封面
    """
    try:
        # 创建标准尺寸封面 (2:3 比例)
        img = Image.new('RGB', (300, 450), color=(245, 245, 245))
        draw = ImageDraw.Draw(img)
        
        # 添加装饰元素
        # 左上角和右下角的水墨风装饰
        for i in range(30):
            # 左上角装饰
            x1, y1 = 0, 0
            x2, y2 = 100 - i*3, 100 - i*3
            opacity = 255 - i*8
            if opacity < 0:
                opacity = 0
            draw.rectangle((x1, y1, x2, y2), fill=(230, 230, 230, opacity))
            
            # 右下角装饰
            x1, y1 = 300, 450
            x2, y2 = 200 + i*3, 350 + i*3
            draw.rectangle((x2, y2, x1, y1), fill=(230, 230, 230, opacity))
        
        # 添加边框
        draw.rectangle((20, 20, 280, 430), outline=(200, 200, 200), width=2)
        
        try:
            from PIL import ImageFont
            
            # 尝试加载字体
            try:
                # 这里可以尝试加载系统字体，但如果失败就使用默认字体
                font_title = ImageFont.load_default()
                font_author = ImageFont.load_default()
                font_publisher = ImageFont.load_default()
            except Exception:
                font_title = None
                font_author = None
                font_publisher = None
            
            # 处理标题，实现自动换行
            title_wrapped = []
            max_chars = 20
            
            # 如果标题太长，拆分成多行
            if len(title) > max_chars:
                words = title.split()
                line = ""
                for word in words:
                    test_line = line + " " + word if line else word
                    if len(test_line) <= max_chars:
                        line = test_line
                    else:
                        title_wrapped.append(line)
                        line = word
                if line:
                    title_wrapped.append(line)
            else:
                title_wrapped = [title]
            
            # 绘制标题
            y_pos = 120
            for line in title_wrapped:
                if font_title:
                    # 居中绘制
                    text_width = font_title.getbbox(line)[2]
                    x_pos = (300 - text_width) // 2
                    draw.text((x_pos, y_pos), line, fill=(50, 50, 50), font=font_title)
                else:
                    # 如果没有字体，使用简单绘制
                    draw.text((50, y_pos), line, fill=(50, 50, 50))
                y_pos += 30
            
            # 绘制作者名
            if author:
                author_text = f"作者: {author}"
                if font_author:
                    author_width = font_author.getbbox(author_text)[2]
                    author_x = (300 - author_width) // 2
                    draw.text((author_x, 280), author_text, fill=(100, 100, 100), font=font_author)
                else:
                    draw.text((50, 280), author_text, fill=(100, 100, 100))
            
            # 添加出版信息
            publisher_text = "墨韵图书共享平台"
            if font_publisher:
                pub_width = font_publisher.getbbox(publisher_text)[2]
                pub_x = (300 - pub_width) // 2
                draw.text((pub_x, 400), publisher_text, fill=(150, 150, 150), font=font_publisher)
            else:
                draw.text((80, 400), publisher_text, fill=(150, 150, 150))
                
        except ImportError:
            # 如果无法导入字体，使用简单绘制
            draw.text((50, 120), title[:20], fill=(50, 50, 50))
            if len(title) > 20:
                draw.text((50, 150), title[20:40], fill=(50, 50, 50))
            if author:
                draw.text((50, 280), f"作者: {author[:20]}", fill=(100, 100, 100))
            draw.text((80, 400), "墨韵图书共享平台", fill=(150, 150, 150))
        
        # 保存封面
        img.save(output_path, quality=90)
        return True
        
    except Exception as e:
        print(f"创建默认封面失败: {e}")
        return False

# 确保默认头像存在
def ensure_default_avatar():
    default_avatar_path = os.path.join(app.config['AVATAR_FOLDER'], 'default.png')
    if not os.path.exists(default_avatar_path) or os.path.getsize(default_avatar_path) == 0:
        # 如果默认头像不存在或为空，则创建简单的默认头像
        # 这里简单处理，实际应用可能需要添加一个真实的默认头像图片
        try:
            # 创建一个100x100的灰色头像
            img = Image.new('RGB', (100, 100), color=(200, 200, 200))
            draw = ImageDraw.Draw(img)
            
            # 添加一个占位符圆形
            draw.ellipse((20, 20, 80, 80), fill=(150, 150, 150))
            
            # 保存到默认头像路径
            img.save(default_avatar_path)
            print(f"创建默认头像: {default_avatar_path}")
        except Exception as e:
            print(f"创建默认头像失败: {e}")
            # 如果出错，至少创建一个非空文件
            with open(default_avatar_path, 'wb') as f:
                # 写入一个简单的PNG文件头
                f.write(b'\x89PNG\r\n\x1a\n')
                # 添加一些额外的字节确保文件非空
                f.write(b'\x00\x00\x00\rIHDR\x00\x00\x00d\x00\x00\x00d')
                f.write(b'\x08\x06\x00\x00\x00\x1f\xf3\xffa')

# 确保默认封面存在
def ensure_default_cover():
    default_cover_path = os.path.join(app.config['COVER_FOLDER'], 'default_cover.jpg')
    if not os.path.exists(default_cover_path) or os.path.getsize(default_cover_path) == 0:
        # 如果默认封面不存在或为空，则创建一个简单的默认封面
        try:
            # 创建一个300x450的默认封面
            img = Image.new('RGB', (300, 450), color=(245, 245, 245))
            draw = ImageDraw.Draw(img)
            
            # 添加简单的装饰
            # 边框
            draw.rectangle((20, 20, 280, 430), outline=(200, 200, 200), width=2)
            
            # 书脊
            draw.rectangle((50, 50, 80, 400), fill=(230, 230, 230))
            
            # 书籍图标
            draw.rectangle((120, 180, 180, 240), fill=(200, 200, 200))
            draw.rectangle((130, 190, 170, 230), fill=(245, 245, 245))
            draw.line((130, 200, 170, 200), fill=(180, 180, 180), width=1)
            draw.line((130, 210, 170, 210), fill=(180, 180, 180), width=1)
            draw.line((130, 220, 170, 220), fill=(180, 180, 180), width=1)
            
            # 添加文字
            try:
                from PIL import ImageFont
                font = ImageFont.load_default()
                draw.text((100, 350), "墨韵图书", fill=(100, 100, 100), font=font)
                draw.text((100, 380), "默认封面", fill=(100, 100, 100), font=font)
            except Exception:
                draw.text((100, 350), "墨韵图书", fill=(100, 100, 100))
                draw.text((100, 380), "默认封面", fill=(100, 100, 100))
                
            # 保存到默认封面路径
            img.save(default_cover_path, quality=90)
            print(f"创建默认封面: {default_cover_path}")
        except Exception as e:
            print(f"创建默认封面失败: {e}")
            # 如果出错，至少创建一个非空文件
            with open(default_cover_path, 'wb') as f:
                # 写入一个简单的JPG文件头
                f.write(b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00')
                f.write(b'\xff\xdb\x00C\x00\x08\x06\x06\x07\x06\x05\x08\x07\x07\x07\t\t\x08\n\x0c\x14\r\x0c\x0b\x0b')

# 路由
@app.route('/')
def index():
    popular_books = Book.query.filter_by(status='approved').order_by(Book.views.desc()).limit(6).all()
    categories = Category.query.all()
    return render_template('index.html', books=popular_books, categories=categories)

@app.route('/search')
def search():
    query = request.args.get('q', '')
    category_id = request.args.get('category', type=int)
    page = request.args.get('page', 1, type=int)
    
    books_query = Book.query.filter_by(status='approved')
    
    if query:
        books_query = books_query.filter(
            or_(
                Book.title.ilike(f'%{query}%'),
                Book.author.ilike(f'%{query}%'),
                Book.tags.ilike(f'%{query}%')
            )
        )
    
    if category_id:
        books_query = books_query.filter_by(category_id=category_id)
    
    books = books_query.paginate(page=page, per_page=app.config['BOOKS_PER_PAGE'])
    categories = Category.query.all()
    
    return render_template('search.html', books=books, categories=categories, query=query, category_id=category_id)

@app.route('/profile')
@login_required
def profile():
    user = current_user
    favorite_books = user.favorite_books
    uploaded_books = Book.query.filter_by(uploader_id=user.id).all()
    notifications = user.notifications.order_by(Notification.created_at.desc()).limit(10).all()
    
    # 获取收藏的帖子（最新5篇）
    favorite_posts = Post.query.join(post_favorites).filter(
        post_favorites.c.user_id == user.id,
        Post.status == 'approved'
    ).order_by(Post.created_at.desc()).limit(5).all()
    
    # 检查是否有更多收藏的帖子
    more_favorite_posts = Post.query.join(post_favorites).filter(
        post_favorites.c.user_id == user.id,
        Post.status == 'approved'
    ).count() > 5
    
    # 更新最后访问时间
    user.last_seen = datetime.now(UTC)
    db.session.commit()
    
    # 记录活动
    record_activity(user.id, 'profile_view')
    
    return render_template('profile.html', user=user, favorite_books=favorite_books, 
                         uploaded_books=uploaded_books, notifications=notifications,
                         favorite_posts=favorite_posts, more_favorite_posts=more_favorite_posts)

@app.route('/profile/update', methods=['POST'])
@login_required
def update_profile():
    if 'avatar' in request.files:
        avatar_file = request.files['avatar']
        if avatar_file and avatar_file.filename and allowed_file(avatar_file.filename, 'avatar'):
            # 处理头像上传
            filename = secure_filename(f"{current_user.id}_{int(datetime.now(UTC).timestamp())}.{avatar_file.filename.rsplit('.', 1)[1].lower()}")
            avatar_path = os.path.join(app.config['AVATAR_FOLDER'], filename)
            avatar_file.save(avatar_path)
            
            # 调整头像大小
            try:
                with Image.open(avatar_path) as img:
                    img = img.resize((150, 150))
                    img.save(avatar_path)
            except Exception as e:
                print(f"处理头像时出错: {e}")
            
            # 更新用户头像
            current_user.avatar = filename
            db.session.commit()
            record_activity(current_user.id, 'update_avatar')
            flash('头像更新成功', 'success')
            return redirect(url_for('profile'))
    
    # 更新个人简介
    bio = request.form.get('bio')
    if bio is not None:
        current_user.bio = bio
        db.session.commit()
        record_activity(current_user.id, 'update_bio')
        flash('个人资料更新成功', 'success')
    
    return redirect(url_for('profile'))

@app.route('/book/<int:book_id>/favorite', methods=['POST'])
@login_required
def toggle_favorite(book_id):
    book = Book.query.get_or_404(book_id)
    if book in current_user.favorite_books:
        current_user.favorite_books.remove(book)
        action = 'unfavorited'
        record_activity(current_user.id, 'unfavorite_book', book_id)
    else:
        current_user.favorite_books.append(book)
        action = 'favorited'
        record_activity(current_user.id, 'favorite_book', book_id)
        
        # 创建通知
        notification = Notification(
            user_id=book.uploader_id,
            content=f'{current_user.username} 收藏了您的图书《{book.title}》',
            type='favorite'
        )
        db.session.add(notification)
    
    db.session.commit()
    return jsonify({'status': 'success', 'action': action})

@app.route('/notifications/mark-read', methods=['POST'])
@login_required
def mark_notifications_read():
    current_user.notifications.filter_by(is_read=False).update({'is_read': True})
    db.session.commit()
    return jsonify({'status': 'success'})

@app.route('/circle/<int:circle_id>')
@login_required
def circle_detail(circle_id):
    circle = Circle.query.get_or_404(circle_id)
    
    # 只有圈子成员可以查看圈子内容
    if current_user not in circle.members and not current_user.is_admin:
        flash('您不是该圈子的成员，无法查看内容', 'warning')
        return redirect(url_for('circles'))
    
    posts = Post.query.filter_by(circle_id=circle_id, status='approved').order_by(Post.created_at.desc()).all()
    record_activity(current_user.id, 'circle_view', circle_id)
    return render_template('circle_detail.html', circle=circle, posts=posts)

@app.route('/book/<int:book_id>')
@login_required
def view_book(book_id):
    book = Book.query.get_or_404(book_id)
    
    # 只有管理员或上传者可以查看待审核的书籍
    if book.status != 'approved' and not current_user.is_admin and book.uploader_id != current_user.id:
        flash('该图书正在审核中，暂不可查看', 'warning')
        return redirect(url_for('books'))
    
    book.views += 1
    db.session.commit()
    
    # 记录阅读活动
    record_activity(current_user.id, 'book_view', book_id)
    
    # 获取用户的阅读进度
    reading_progress = ReadingProgress.query.filter_by(
        user_id=current_user.id, book_id=book.id
    ).first()
    
    # 获取用户书签
    bookmarks = Bookmark.query.filter_by(
        user_id=current_user.id, book_id=book.id
    ).all()
    
    # 获取相关图书推荐（同一分类的其他书籍，按浏览量排序）
    related_books = Book.query.filter(
        Book.category_id == book.category_id, 
        Book.id != book.id,
        Book.status == 'approved'
    ).order_by(Book.views.desc()).limit(5).all()
    
    # 获取图书评论
    comments = BookComment.query.filter_by(book_id=book.id).order_by(BookComment.created_at.desc()).all()
    
    return render_template('view_book.html', 
                         book=book, 
                         related_books=related_books,
                         reading_progress=reading_progress,
                         bookmarks=bookmarks,
                         comments=comments)

@app.route('/book/<int:book_id>/comment', methods=['POST'])
@login_required
def add_book_comment(book_id):
    book = Book.query.get_or_404(book_id)
    content = request.form.get('content')
    rating = request.form.get('rating', type=int)
    
    if not content:
        flash('评论内容不能为空', 'danger')
        return redirect(url_for('view_book', book_id=book_id))
    
    # 检查评分是否有效
    if rating is not None and (rating < 1 or rating > 5):
        flash('评分必须在1-5星之间', 'danger')
        return redirect(url_for('view_book', book_id=book_id))
    
    comment = BookComment(
        content=content,
        rating=rating,
        author_id=current_user.id,
        book_id=book_id
    )
    db.session.add(comment)
    
    # 更新图书平均评分
    if rating:
        book.rating_count += 1
        book.rating = ((book.rating * (book.rating_count - 1)) + rating) / book.rating_count
    
    # 为图书上传者创建通知
    if book.uploader_id != current_user.id:
        notification = Notification(
            user_id=book.uploader_id,
            content=f'您的图书《{book.title}》有新评论',
            type='book_comment',
            related_id=book.id
        )
        db.session.add(notification)
    
    db.session.commit()
    record_activity(current_user.id, 'book_comment', book_id)
    flash('评论成功', 'success')
    return redirect(url_for('view_book', book_id=book_id))

@app.route('/book/<int:book_id>/bookmark', methods=['POST'])
@login_required
def add_bookmark(book_id):
    book = Book.query.get_or_404(book_id)
    page = request.form.get('page', type=int)
    note = request.form.get('note', '')
    
    if not page or page < 1:
        flash('请输入有效的页码', 'danger')
        return redirect(url_for('view_book', book_id=book_id))
    
    bookmark = Bookmark(
        user_id=current_user.id,
        book_id=book_id,
        page=page,
        note=note
    )
    db.session.add(bookmark)
    db.session.commit()
    
    record_activity(current_user.id, 'add_bookmark', book_id)
    flash('书签添加成功', 'success')
    return redirect(url_for('view_book', book_id=book_id))

@app.route('/bookmark/<int:bookmark_id>/delete', methods=['POST'])
@login_required
def delete_bookmark(bookmark_id):
    bookmark = Bookmark.query.get_or_404(bookmark_id)
    
    # 检查权限
    if bookmark.user_id != current_user.id:
        return jsonify({'success': False, 'message': '您没有权限删除此书签'})
    
    try:
        db.session.delete(bookmark)
        db.session.commit()
        return jsonify({'success': True})
    except Exception as e:
        db.session.rollback()
        return jsonify({'success': False, 'message': str(e)})

@app.route('/book/<int:book_id>/progress', methods=['POST'])
@login_required
def update_reading_progress(book_id):
    book = Book.query.get_or_404(book_id)
    page = request.form.get('page', type=int)
    
    if not page or page < 1:
        return jsonify({'success': False, 'message': '请输入有效的页码'})
    
    # 查找或创建阅读进度记录
    progress = ReadingProgress.query.filter_by(
        user_id=current_user.id, book_id=book_id
    ).first()
    
    if progress:
        progress.page = page
        progress.last_read = datetime.now(UTC)
    else:
        progress = ReadingProgress(
            user_id=current_user.id,
            book_id=book_id,
            page=page
        )
        db.session.add(progress)
    
    db.session.commit()
    record_activity(current_user.id, 'update_progress', book_id)
    return jsonify({'success': True})

@app.route('/forum')
@login_required
def forum():
    page = request.args.get('page', 1, type=int)
    circle_id = request.args.get('circle_id', type=int)
    sort = request.args.get('sort', 'latest')
    
    # 构建基础查询
    query = Post.query.filter_by(status='approved')
    
    # 如果指定了圈子，只显示该圈子的帖子
    if circle_id:
        query = query.filter_by(circle_id=circle_id)
    
    # 排序
    if sort == 'hot':
        query = query.order_by(Post.likes.desc(), Post.created_at.desc())
    else:  # latest
        query = query.order_by(Post.created_at.desc())
    
    # 分页
    pagination = query.paginate(page=page, per_page=10)
    posts = pagination.items
    
    # 获取当前用户已点赞的帖子ID列表
    liked_posts = db.session.query(post_likes.c.post_id).filter_by(user_id=current_user.id).all()
    liked_post_ids = {post_id for (post_id,) in liked_posts}
    
    # 获取所有圈子
    circles = Circle.query.all()
    
    # 获取当前圈子（如果有）
    current_circle = Circle.query.get(circle_id) if circle_id else None
    
    record_activity(current_user.id, 'browse_forum')
    return render_template('forum.html', 
                         posts=posts, 
                         pagination=pagination,
                         circles=circles,
                         current_circle=current_circle,
                         liked_post_ids=liked_post_ids)

@app.route('/admin')
@login_required
@admin_required
def admin_panel():
    # 获取统计数据
    user_count = User.query.count()
    book_count = Book.query.count()
    post_count = Post.query.count()
    
    # 待审核项目
    pending_books = Book.query.filter_by(status='pending').count()
    pending_posts = Post.query.filter_by(status='pending').count()
    
    # 最近活动
    recent_activities = UserActivity.query.order_by(UserActivity.timestamp.desc()).limit(10).all()
    
    # 活跃用户
    active_users = db.session.query(
        User, func.count(UserActivity.id).label('activity_count')
    ).join(UserActivity).group_by(User.id).order_by(desc('activity_count')).limit(5).all()
    
    # 热门图书
    popular_books = Book.query.filter_by(status='approved').order_by(Book.views.desc()).limit(5).all()
    
    return render_template('admin/dashboard.html',
                         user_count=user_count,
                         book_count=book_count,
                         post_count=post_count,
                         pending_books=pending_books,
                         pending_posts=pending_posts,
                         recent_activities=recent_activities,
                         active_users=active_users,
                         popular_books=popular_books)

@app.route('/admin/books')
@login_required
@admin_required
def admin_books():
    page = request.args.get('page', 1, type=int)
    status = request.args.get('status', 'all')
    
    query = Book.query
    if status != 'all':
        query = query.filter_by(status=status)
    
    books = query.paginate(
        page=page, 
        per_page=20,
        error_out=False
    )
    
    return render_template('admin/books.html', books=books, status=status)

@app.route('/admin/book/<int:book_id>/approve', methods=['POST'])
@login_required
@admin_required
def approve_book(book_id):
    book = Book.query.get_or_404(book_id)
    book.status = 'approved'
    
    # 创建通知
    notification = Notification(
        user_id=book.uploader_id,
        title='图书审核通过',
        content=f'您上传的图书《{book.title}》已审核通过，现在可以在平台上查看。',
        type='book_approved'
    )
    db.session.add(notification)
    db.session.commit()
    
    flash('图书已批准', 'success')
    return redirect(url_for('admin_books'))

@app.route('/admin/book/<int:book_id>/reject', methods=['POST'])
@login_required
@admin_required
def reject_book(book_id):
    book = Book.query.get_or_404(book_id)
    reason = request.form.get('reason', '不符合平台规定')
    book.status = 'rejected'
    
    # 创建通知
    notification = Notification(
        user_id=book.uploader_id,
        title='图书审核未通过',
        content=f'您上传的图书《{book.title}》未通过审核。原因：{reason}',
        type='book_rejected'
    )
    db.session.add(notification)
    db.session.commit()
    
    flash('图书已拒绝', 'success')
    return redirect(url_for('admin_books'))

@app.route('/admin/users')
@login_required
@admin_required
def admin_users():
    page = request.args.get('page', 1, type=int)
    users = User.query.paginate(
        page=page, 
        per_page=20,
        error_out=False
    )
    return render_template('admin/users.html', users=users)

@app.route('/admin/user/<int:user_id>/toggle_admin', methods=['POST'])
@login_required
@admin_required
def toggle_admin(user_id):
    user = User.query.get_or_404(user_id)
    
    # 不能移除自己的管理员权限
    if user.id == current_user.id:
        flash('不能修改自己的管理员状态', 'danger')
        return redirect(url_for('admin_users'))
    
    user.is_admin = not user.is_admin
    db.session.commit()
    
    flash(f'已{"授予" if user.is_admin else "移除"}{user.username}的管理员权限', 'success')
    return redirect(url_for('admin_users'))

@app.route('/admin/categories')
@login_required
@admin_required
def admin_categories():
    categories = Category.query.all()
    return render_template('admin/categories.html', categories=categories)

@app.route('/admin/export_data')
@login_required
@admin_required
def admin_export_data():
    data_type = request.args.get('type', 'users')
    
    if data_type == 'users':
        # 导出用户数据
        users = User.query.all()
        output = io.StringIO()
        writer = csv.writer(output)
        
        writer.writerow(['ID', '用户名', '学号', '是否管理员', '是否教师', '注册时间', '最后登录'])
        for user in users:
            writer.writerow([
                user.id, 
                user.username, 
                user.student_id, 
                '是' if user.is_admin else '否', 
                '是' if user.is_teacher else '否',
                user.created_at.strftime('%Y-%m-%d %H:%M'),
                user.last_seen.strftime('%Y-%m-%d %H:%M') if user.last_seen else ''
            ])
        
        output.seek(0)
        return Response(
            output,
            mimetype="text/csv",
            headers={"Content-Disposition": "attachment;filename=users.csv"}
        )
    
    elif data_type == 'books':
        # 导出图书数据
        books = Book.query.all()
        output = io.StringIO()
        writer = csv.writer(output)
        
        writer.writerow(['ID', '书名', '作者', '分类', '上传者', '状态', '上传时间', '浏览量', '评分'])
        for book in books:
            writer.writerow([
                book.id, 
                book.title, 
                book.author, 
                book.category.name if book.category else '', 
                book.uploader.username if book.uploader else '',
                book.status,
                book.created_at.strftime('%Y-%m-%d %H:%M'),
                book.views,
                f"{book.rating:.1f}" if book.rating else '无评分'
            ])
        
        output.seek(0)
        return Response(
            output,
            mimetype="text/csv",
            headers={"Content-Disposition": "attachment;filename=books.csv"}
        )
    
    else:
        flash('不支持的数据类型', 'danger')
        return redirect(url_for('admin_panel'))

@app.route('/admin/statistics')
@login_required
@admin_required
def admin_statistics():
    # 统计近30天的活动数据
    thirty_days_ago = datetime.now(UTC) - timedelta(days=30)
    
    # 每日活动统计
    daily_activities = db.session.query(
        func.date(UserActivity.timestamp).label('date'),
        func.count(UserActivity.id).label('count')
    ).filter(UserActivity.timestamp >= thirty_days_ago).group_by('date').all()
    
    # 活动类型统计
    activity_types = db.session.query(
        UserActivity.activity_type,
        func.count(UserActivity.id).label('count')
    ).group_by(UserActivity.activity_type).all()
    
    # 图书分类统计
    book_categories = db.session.query(
        Category.name,
        func.count(Book.id).label('count')
    ).join(Book, Book.category_id == Category.id).group_by(Category.name).all()
    
    return render_template('admin/statistics.html',
                         daily_activities=daily_activities,
                         activity_types=activity_types,
                         book_categories=book_categories)

from flask import Response

@app.route('/post/<int:post_id>/like', methods=['POST'])
@login_required
def like_post(post_id):
    post = Post.query.get_or_404(post_id)
    
    # 查询用户是否已点赞
    is_liked = db.session.query(post_likes).filter_by(
        user_id=current_user.id, post_id=post_id
    ).first() is not None
    
    if is_liked:
        # 已点赞，取消点赞
        db.session.execute(post_likes.delete().where(
            and_(post_likes.c.user_id == current_user.id, post_likes.c.post_id == post_id)
        ))
        post.likes -= 1
        action = 'unliked'
    else:
        # 未点赞，添加点赞
        db.session.execute(post_likes.insert().values(
            user_id=current_user.id, post_id=post_id
        ))
        post.likes += 1
        action = 'liked'
        
        # 添加通知
        if post.author_id != current_user.id:
            notification = Notification(
                user_id=post.author_id,
                content=f'{current_user.username} 赞了您的帖子《{post.title}》',
                type='post_like',
                related_id=post_id
            )
            db.session.add(notification)
    
    db.session.commit()
    record_activity(current_user.id, f'post_{action}', post_id)
    
    return jsonify({'success': True, 'action': action, 'likes': post.likes})

@app.route('/upload', methods=['GET', 'POST'])
@login_required
def upload_book():
    if request.method == 'POST':
        title = request.form.get('title')
        author = request.form.get('author')
        description = request.form.get('description')
        category_id = request.form.get('category_id')
        tags = request.form.get('tags', '')
        book_file = request.files.get('book_file')
        cover_file = request.files.get('cover_file')
        
        if not title or not author or not book_file:
            flash('请填写所有必填字段', 'danger')
            return redirect(url_for('upload_book'))
            
        if not category_id:
            flash('请选择图书分类', 'danger')
            return redirect(url_for('upload_book'))
            
        # 检查文件类型
        if not allowed_file(book_file.filename, 'book'):
            flash('不支持的文件格式', 'danger')
            return redirect(url_for('upload_book'))
            
        # 生成唯一文件名，避免文件名冲突
        timestamp = int(datetime.now(UTC).timestamp())
        book_filename = secure_filename(f"{timestamp}_{book_file.filename}")
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], book_filename)
        book_file.save(file_path)
        
        # 处理封面
        cover_path = None
        
        # 1. 如果用户上传了封面图片
        if cover_file and cover_file.filename:
            if allowed_file(cover_file.filename, 'cover'):
                cover_extension = cover_file.filename.rsplit('.', 1)[1].lower()
                cover_filename = secure_filename(f"{timestamp}_cover.{cover_extension}")
                cover_full_path = os.path.join(app.config['COVER_FOLDER'], cover_filename)
                cover_file.save(cover_full_path)
                
                # 裁剪和调整封面大小
                try:
                    with Image.open(cover_full_path) as img:
                        # 裁剪为合适的宽高比 2:3
                        width, height = img.size
                        target_ratio = 2/3  # 宽:高 = 2:3
                        current_ratio = width/height
                        
                        if current_ratio > target_ratio:
                            # 图像太宽，裁剪宽度
                            new_width = int(height * target_ratio)
                            left = (width - new_width) // 2
                            img = img.crop((left, 0, left + new_width, height))
                        elif current_ratio < target_ratio:
                            # 图像太高，裁剪高度
                            new_height = int(width / target_ratio)
                            top = (height - new_height) // 2
                            img = img.crop((0, top, width, top + new_height))
                        
                        # 调整大小到标准尺寸
                        img = img.resize((300, 450), Image.LANCZOS)
                        img.save(cover_full_path, quality=90)
                        cover_path = cover_filename
                except Exception as e:
                    print(f"处理封面图片时出错: {e}")
                    cover_path = None
        
        # 2. 如果没有上传封面或处理失败，尝试从电子书生成
        if not cover_path:
            file_ext = os.path.splitext(book_filename)[1].lower()
            cover_filename = f"{timestamp}_auto_cover.jpg"
            cover_full_path = os.path.join(app.config['COVER_FOLDER'], cover_filename)
            
            if file_ext == '.pdf':
                # 从PDF提取封面
                if extract_pdf_cover(file_path, cover_full_path):
                    cover_path = cover_filename
            
            # 3. 如果前两种方法失败，创建默认封面
            if not cover_path:
                if create_default_cover(title, author, cover_full_path):
                    cover_path = cover_filename
        
        # 创建图书记录
        book = Book(
            title=title,
            author=author,
            description=description,
            file_path=book_filename,
            cover_path=cover_path,
            category_id=category_id,
            uploader_id=current_user.id,
            views=0,
            tags=tags,
            status='pending' if not current_user.is_admin else 'approved'
        )
        db.session.add(book)
        db.session.commit()
        
        record_activity(current_user.id, 'upload_book', book.id)
        
        if book.status == 'pending':
            flash('图书上传成功！等待管理员审核后将显示在图书列表中。', 'success')
        else:
            flash('图书上传成功！', 'success')
        return redirect(url_for('books'))
        
    categories = Category.query.all()
    return render_template('upload_book.html', categories=categories)

@app.route('/book/<int:book_id>/download')
@login_required
def download_book(book_id):
    book = Book.query.get_or_404(book_id)
    
    # 只有管理员或上传者可以下载待审核的书籍
    if book.status != 'approved' and not current_user.is_admin and book.uploader_id != current_user.id:
        flash('该图书正在审核中，暂不可下载', 'warning')
        return redirect(url_for('books'))
    
    record_activity(current_user.id, 'download_book', book_id)
    return send_from_directory(app.config['UPLOAD_FOLDER'], book.file_path, as_attachment=True)

@app.route('/books')
@login_required
def books():
    page = request.args.get('page', 1, type=int)
    category_id = request.args.get('category', type=int)
    
    query = Book.query.filter_by(status='approved')
    if category_id:
        query = query.filter_by(category_id=category_id)
    
    books = query.order_by(Book.created_at.desc()).paginate(
        page=page, per_page=app.config['BOOKS_PER_PAGE'])
    categories = Category.query.all()
    
    record_activity(current_user.id, 'browse_books')
    return render_template('books.html', books=books, categories=categories)

@app.route('/post/<int:post_id>')
@login_required
def view_post(post_id):
    post = Post.query.get_or_404(post_id)
    
    # 只有管理员或作者可以查看待审核的帖子
    if post.status != 'approved' and not current_user.is_admin and post.author_id != current_user.id:
        flash('该帖子正在审核中，暂不可查看', 'warning')
        return redirect(url_for('forum'))
    
    # 检查当前用户是否点赞过该帖子
    is_liked = db.session.query(post_likes).filter_by(
        user_id=current_user.id, post_id=post_id
    ).first() is not None
    
    # 检查当前用户是否收藏过该帖子
    is_favorited = db.session.query(post_favorites).filter_by(
        user_id=current_user.id, post_id=post_id
    ).first() is not None
    
    record_activity(current_user.id, 'view_post', post_id)
    return render_template('view_post.html', post=post, is_liked=is_liked, is_favorited=is_favorited)

@app.route('/book/<int:book_id>/read')
@login_required
def read_book(book_id):
    book = Book.query.get_or_404(book_id)
    
    # 只有管理员或上传者可以阅读待审核的书籍
    if book.status != 'approved' and not current_user.is_admin and book.uploader_id != current_user.id:
        flash('该图书正在审核中，暂不可阅读', 'warning')
        return redirect(url_for('books'))
    
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], book.file_path)
    
    # 检查文件是否存在
    if not os.path.exists(file_path):
        flash('文件不存在或已被删除', 'danger')
        return redirect(url_for('books'))
    
    # 记录阅读活动
    record_activity(current_user.id, 'read_book', book_id)
    
    # 获取文件扩展名
    file_ext = os.path.splitext(book.file_path)[1].lower()
    
    # 根据不同文件类型处理
    if file_ext == '.pdf':
        # PDF文件可以直接通过浏览器内置PDF查看器查看
        response = send_file(
            file_path, 
            mimetype='application/pdf'
        )
        # 设置Content-Disposition为inline，确保浏览器内显示而不是下载
        response.headers['Content-Disposition'] = f'inline; filename="{book.title}.pdf"'
        return response
    elif file_ext in ['.jpg', '.jpeg', '.png', '.gif']:
        # 图片文件
        return send_file(file_path, mimetype=f'image/{file_ext[1:]}')
    elif file_ext in ['.epub', '.mobi']:
        # 电子书文件，尝试在线阅读
        # 对于epub和mobi格式，我们可以尝试返回HTML页面进行在线阅读
        # 注意：这里只是示例，实际实现可能需要更复杂的电子书阅读器组件
        return render_template('ebook_reader.html', book=book)
    else:
        # 其他类型文件，提供下载
        return send_file(file_path, as_attachment=True)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    
    if request.method == 'POST':
        student_id = request.form.get('student_id')
        password = request.form.get('password')
        user = User.query.filter_by(student_id=student_id).first()
        
        if user and user.check_password(password):
            login_user(user, remember=True)
            user.last_seen = datetime.now(UTC)
            db.session.commit()
            record_activity(user.id, 'login')
            next_page = request.args.get('next')
            if next_page:
                return redirect(next_page)
            return redirect(url_for('index'))
        else:
            flash('用户名或密码错误', 'danger')
    
    return render_template('login.html')

@app.route('/logout')
@login_required
def logout():
    if current_user.is_authenticated:
        record_activity(current_user.id, 'logout')
    logout_user()
    return redirect(url_for('index'))

@app.route('/register', methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    
    if request.method == 'POST':
        student_id = request.form.get('student_id')
        username = request.form.get('username')
        password = request.form.get('password')
        email = request.form.get('email')
        is_teacher = 'is_teacher' in request.form
        
        # 验证学号是否已存在
        if User.query.filter_by(student_id=student_id).first():
            flash('该学号已被注册', 'danger')
            return render_template('register.html')
        
        # 验证邮箱是否已存在
        if email and User.query.filter_by(email=email).first():
            flash('该邮箱已被注册', 'danger')
            return render_template('register.html')
        
        # 创建新用户
        user = User(
            student_id=student_id,
            username=username,
            email=email,
            is_teacher=is_teacher
        )
        user.set_password(password)
        
        # 第一个注册的用户设为管理员
        if User.query.count() == 0:
            user.is_admin = True
        
        db.session.add(user)
        db.session.commit()
        
        flash('注册成功，请登录', 'success')
        return redirect(url_for('login'))
    
    return render_template('register.html')

@app.route('/circles')
@login_required
def circles():
    # 获取所有圈子
    all_circles = Circle.query.all()
    
    # 查询用户已加入的圈子
    joined_circles = []
    for circle in all_circles:
        if current_user in circle.members:
            joined_circles.append(circle.id)
    
    # 记录用户活动
    record_activity(current_user.id, 'view_circles')
    
    return render_template('circles.html', circles=all_circles, joined_circle_ids=joined_circles)

@app.route('/create_post', methods=['GET', 'POST'])
@login_required
def create_post():
    circle_id = request.args.get('circle_id', type=int)
    
    if request.method == 'POST':
        title = request.form.get('title')
        content = request.form.get('content')
        circle_id = request.form.get('circle_id', type=int)
        
        if not title or not content:
            flash('标题和内容不能为空', 'danger')
            return redirect(url_for('create_post', circle_id=circle_id))
        
        # 创建新帖子
        post = Post(
            title=title,
            content=content,
            author_id=current_user.id,
            circle_id=circle_id,
            status='approved' if current_user.is_admin else 'pending'
        )
        db.session.add(post)
        db.session.commit()
        
        # 记录活动
        record_activity(current_user.id, 'create_post', post.id)
        
        # 如果不是管理员发布，给管理员发送审核通知
        if not current_user.is_admin:
            admins = User.query.filter_by(is_admin=True).all()
            for admin in admins:
                notification = Notification(
                    user_id=admin.id,
                    title='新帖子待审核',
                    content=f'用户 {current_user.username} 发布了一篇新帖子《{post.title}》，等待审核',
                    type='post_pending',
                    related_id=post.id
                )
                db.session.add(notification)
            db.session.commit()
            flash('帖子已提交，等待管理员审核', 'info')
        else:
            flash('帖子发布成功', 'success')
        
        # 如果有指定圈子，跳转到圈子页面
        if circle_id:
            return redirect(url_for('circle_detail', circle_id=circle_id))
        
        return redirect(url_for('forum'))
    
    # 获取所有圈子供选择
    circles = Circle.query.all()
    
    return render_template('create_post.html', circles=circles, circle_id=circle_id)

@app.route('/create_circle', methods=['GET', 'POST'])
@login_required
def create_circle():
    # 只有教师和管理员可以创建圈子
    if not current_user.is_teacher and not current_user.is_admin:
        flash('只有教师和管理员可以创建圈子', 'danger')
        return redirect(url_for('circles'))
    
    if request.method == 'POST':
        name = request.form.get('name')
        description = request.form.get('description')
        
        if not name:
            flash('圈子名称不能为空', 'danger')
            return render_template('create_circle.html')
        
        # 检查圈子名称是否已存在
        if Circle.query.filter_by(name=name).first():
            flash('该圈子名称已存在', 'danger')
            return render_template('create_circle.html')
        
        # 创建新圈子
        circle = Circle(
            name=name,
            description=description,
            teacher_id=current_user.id
        )
        
        # 创建者自动成为圈子成员
        circle.members.append(current_user)
        
        db.session.add(circle)
        db.session.commit()
        
        # 记录活动
        record_activity(current_user.id, 'create_circle', circle.id)
        
        flash('圈子创建成功', 'success')
        return redirect(url_for('circle_detail', circle_id=circle.id))
    
    return render_template('create_circle.html')

@app.route('/admin/categories/create', methods=['GET', 'POST'])
@login_required
@admin_required
def create_category():
    if request.method == 'POST':
        name = request.form.get('name')
        description = request.form.get('description')
        
        if not name:
            flash('分类名称不能为空', 'danger')
            return render_template('admin/create_category.html')
        
        # 检查分类名称是否已存在
        if Category.query.filter_by(name=name).first():
            flash('该分类名称已存在', 'danger')
            return render_template('admin/create_category.html')
        
        # 创建新分类
        category = Category(
            name=name,
            description=description
        )
        db.session.add(category)
        db.session.commit()
        
        flash('分类创建成功', 'success')
        return redirect(url_for('admin_categories'))
    
    return render_template('admin/create_category.html')

@app.route('/admin/category/<int:category_id>/edit', methods=['POST'])
@login_required
@admin_required
def edit_category(category_id):
    category = Category.query.get_or_404(category_id)
    
    name = request.form.get('name')
    description = request.form.get('description')
    
    if not name:
        flash('分类名称不能为空', 'danger')
        return redirect(url_for('admin_categories'))
    
    # 检查是否有重名分类（除了自己）
    existing_category = Category.query.filter(
        Category.name == name, 
        Category.id != category_id
    ).first()
    
    if existing_category:
        flash('已存在同名分类', 'danger')
        return redirect(url_for('admin_categories'))
    
    category.name = name
    category.description = description
    db.session.commit()
    
    flash('分类已更新', 'success')
    return redirect(url_for('admin_categories'))

@app.route('/admin/category/<int:category_id>/delete', methods=['POST'])
@login_required
@admin_required
def delete_category(category_id):
    category = Category.query.get_or_404(category_id)
    
    # 将该分类下的图书移至未分类
    for book in category.books:
        book.category_id = None
    
    db.session.delete(category)
    db.session.commit()
    
    flash('分类已删除', 'success')
    return redirect(url_for('admin_categories'))

@app.route('/circle/<int:circle_id>/join', methods=['POST'])
@login_required
def join_circle(circle_id):
    # 此函数已不再允许学生自行加入圈子，只保留接受邀请的功能
    # 该功能通过 respond_to_invitation 函数实现
    flash('加入圈子需要教师邀请', 'info')
    return redirect(url_for('circles'))

@app.route('/circle/<int:circle_id>/invite', methods=['GET', 'POST'])
@login_required
def invite_to_circle(circle_id):
    circle = Circle.query.get_or_404(circle_id)
    
    # 只有圈子创建者(教师)或管理员可以邀请
    if circle.teacher_id != current_user.id and not current_user.is_admin:
        flash('只有圈子创建者可以邀请成员', 'danger')
        return redirect(url_for('circle_detail', circle_id=circle_id))
    
    if request.method == 'POST':
        student_id = request.form.get('student_id')
        
        if not student_id:
            flash('请输入学号', 'danger')
            return redirect(url_for('invite_to_circle', circle_id=circle_id))
            
        # 根据学号查找用户
        user = User.query.filter_by(student_id=student_id).first()
        
        if not user:
            flash(f'未找到学号为 {student_id} 的用户', 'danger')
            return redirect(url_for('invite_to_circle', circle_id=circle_id))
            
        # 检查用户是否已经在圈子中
        if user in circle.members:
            flash(f'用户 {user.username} 已经是圈子成员', 'info')
            return redirect(url_for('invite_to_circle', circle_id=circle_id))
            
        # 创建邀请通知
        notification = Notification(
            user_id=user.id,
            title=f'邀请加入圈子 {circle.name}',
            content=f'老师 {current_user.username} 邀请您加入学习圈子 "{circle.name}"',
            type='invite',
            related_id=circle_id
        )
        db.session.add(notification)
        db.session.commit()
        
        flash(f'已向用户 {user.username} 发送邀请', 'success')
        return redirect(url_for('circle_detail', circle_id=circle_id))
        
    return render_template('invite_to_circle.html', circle=circle)

@app.route('/circle/<int:circle_id>/leave', methods=['POST'])
@login_required
def leave_circle(circle_id):
    circle = Circle.query.get_or_404(circle_id)
    
    # 检查是否为圈子创建者，创建者不能离开圈子
    if circle.teacher_id == current_user.id:
        flash('作为圈子创建者，您不能离开圈子', 'danger')
        return redirect(url_for('circle_detail', circle_id=circle_id))
    
    if current_user in circle.members:
        circle.members.remove(current_user)
        db.session.commit()
        
        # 记录活动
        record_activity(current_user.id, 'leave_circle', circle_id)
        
        flash('已退出圈子', 'success')
    else:
        flash('您不是该圈子的成员', 'info')
    
    return redirect(url_for('circles'))

@app.route('/post/<int:post_id>/comment', methods=['POST'])
@login_required
def add_comment(post_id):
    post = Post.query.get_or_404(post_id)
    content = request.form.get('content')
    
    if not content:
        flash('评论内容不能为空', 'danger')
        return redirect(url_for('view_post', post_id=post_id))
    
    # 创建新评论
    comment = Comment(
        content=content,
        author_id=current_user.id,
        post_id=post_id,
        status='approved' if current_user.is_admin else 'pending'
    )
    db.session.add(comment)
    
    # 给帖子作者发送通知（如果不是自己的帖子）
    if post.author_id != current_user.id:
        notification = Notification(
            user_id=post.author_id,
            content=f'{current_user.username} 评论了您的帖子《{post.title}》',
            type='post_comment',
            related_id=post_id
        )
        db.session.add(notification)
    
    db.session.commit()
    
    # 记录活动
    record_activity(current_user.id, 'comment_post', post_id)
    
    if comment.status == 'pending':
        flash('评论已提交，等待管理员审核', 'info')
    else:
        flash('评论发布成功', 'success')
    
    return redirect(url_for('view_post', post_id=post_id))

@app.route('/post/<int:post_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_post(post_id):
    post = Post.query.get_or_404(post_id)
    
    # 检查权限：只有帖子作者或管理员才能编辑
    if post.author_id != current_user.id and not current_user.is_admin:
        flash('您没有权限编辑此帖子', 'danger')
        return redirect(url_for('view_post', post_id=post_id))
    
    if request.method == 'POST':
        title = request.form.get('title')
        content = request.form.get('content')
        
        if not title or not content:
            flash('标题和内容不能为空', 'danger')
            return render_template('edit_post.html', post=post)
        
        # 更新帖子内容
        post.title = title
        post.content = content
        post.updated_at = datetime.now(UTC)
        
        db.session.commit()
        
        # 记录活动
        record_activity(current_user.id, 'edit_post', post_id)
        
        flash('帖子已更新', 'success')
        return redirect(url_for('view_post', post_id=post_id))
    
    return render_template('edit_post.html', post=post)

@app.route('/post/<int:post_id>/delete', methods=['POST'])
@login_required
def delete_post(post_id):
    post = Post.query.get_or_404(post_id)
    
    # 检查权限：只有帖子作者或管理员才能删除
    if post.author_id != current_user.id and not current_user.is_admin:
        return jsonify({'success': False, 'message': '您没有权限删除此帖子'})
    
    try:
        # 删除帖子相关的评论
        Comment.query.filter_by(post_id=post_id).delete()
        
        # 删除帖子
        db.session.delete(post)
        db.session.commit()
        
        # 记录活动
        record_activity(current_user.id, 'delete_post', post_id)
        
        return jsonify({'success': True})
    except Exception as e:
        db.session.rollback()
        return jsonify({'success': False, 'message': str(e)})

@app.route('/comment/<int:comment_id>/delete', methods=['POST'])
@login_required
def delete_comment(comment_id):
    comment = Comment.query.get_or_404(comment_id)
    
    # 检查权限：只有评论作者或管理员才能删除
    if comment.author_id != current_user.id and not current_user.is_admin:
        return jsonify({'success': False, 'message': '您没有权限删除此评论'})
    
    try:
        db.session.delete(comment)
        db.session.commit()
        
        # 记录活动
        record_activity(current_user.id, 'delete_comment', comment.post_id)
        
        return jsonify({'success': True})
    except Exception as e:
        db.session.rollback()
        return jsonify({'success': False, 'message': str(e)})

@app.route('/notification/<int:notification_id>/respond', methods=['POST'])
@login_required
def respond_to_invitation(notification_id):
    notification = Notification.query.get_or_404(notification_id)
    
    # 验证通知是否属于当前用户
    if notification.user_id != current_user.id:
        flash('无权操作此通知', 'danger')
        return redirect(url_for('profile'))
    
    # 检查通知类型
    if notification.type != 'invite':
        flash('不是有效的邀请通知', 'warning')
        return redirect(url_for('profile'))
    
    action = request.form.get('action')
    
    if action == 'accept':
        # 处理接受邀请
        if notification.related_id:
            # 假设related_id是圈子ID
            circle = Circle.query.get(notification.related_id)
            if circle and current_user not in circle.members:
                circle.members.append(current_user)
                record_activity(current_user.id, 'join_circle', circle.id)
                
                # 通知圈子创建者
                if circle.teacher_id and circle.teacher_id != current_user.id:
                    sender_notification = Notification(
                        user_id=circle.teacher_id,
                        content=f'{current_user.username} 接受了您的邀请，加入了圈子"{circle.name}"',
                        type='invite_accepted'
                    )
                    db.session.add(sender_notification)
                
                flash('已成功加入圈子', 'success')
            else:
                flash('无法加入圈子', 'warning')
        else:
            flash('无效的邀请', 'warning')
    
    elif action == 'reject':
        # 处理拒绝邀请
        if notification.related_id:
            # 假设related_id是圈子ID
            circle = Circle.query.get(notification.related_id)
            if circle and circle.teacher_id:
                # 通知圈子创建者
                sender_notification = Notification(
                    user_id=circle.teacher_id,
                    content=f'{current_user.username} 拒绝了您的邀请',
                    type='invite_rejected'
                )
                db.session.add(sender_notification)
                
                flash('已拒绝邀请', 'info')
            else:
                flash('无效的邀请', 'warning')
        else:
            flash('无效的邀请', 'warning')
    
    # 将通知标记为已读
    notification.is_read = True
    db.session.commit()
    
    return redirect(url_for('profile'))

@app.route('/categories')
@login_required
def list_categories():
    categories = Category.query.all()
    return render_template('categories.html', categories=categories)

@app.route('/admin/posts')
@login_required
@admin_required
def admin_posts():
    page = request.args.get('page', 1, type=int)
    status = request.args.get('status', 'all')
    
    query = Post.query
    if status != 'all':
        query = query.filter_by(status=status)
    
    # 默认按创建时间降序排序
    query = query.order_by(Post.created_at.desc())
    
    posts = query.paginate(
        page=page, 
        per_page=20,
        error_out=False
    )
    
    pending_count = Post.query.filter_by(status='pending').count()
    
    return render_template('admin/posts.html', posts=posts, status=status, pending_count=pending_count)

@app.route('/admin/post/<int:post_id>/approve', methods=['POST'])
@login_required
@admin_required
def approve_post(post_id):
    post = Post.query.get_or_404(post_id)
    post.status = 'approved'
    
    # 创建通知
    notification = Notification(
        user_id=post.author_id,
        title='帖子审核通过',
        content=f'您发布的帖子《{post.title}》已审核通过，现在可以在平台上查看。',
        type='post_approved',
        related_id=post.id
    )
    db.session.add(notification)
    db.session.commit()
    
    flash('帖子已批准', 'success')
    return redirect(url_for('admin_posts'))

@app.route('/admin/post/<int:post_id>/reject', methods=['POST'])
@login_required
@admin_required
def reject_post(post_id):
    post = Post.query.get_or_404(post_id)
    reason = request.form.get('reason', '不符合平台规定')
    post.status = 'rejected'
    
    # 创建通知
    notification = Notification(
        user_id=post.author_id,
        title='帖子审核未通过',
        content=f'您发布的帖子《{post.title}》未通过审核。原因：{reason}',
        type='post_rejected',
        related_id=post.id
    )
    db.session.add(notification)
    db.session.commit()
    
    flash('帖子已拒绝', 'success')
    return redirect(url_for('admin_posts'))

@app.route('/notification/<int:notification_id>/view', methods=['GET'])
@login_required
def view_notification(notification_id):
    notification = Notification.query.get_or_404(notification_id)
    
    # 验证通知是否属于当前用户
    if notification.user_id != current_user.id:
        flash('无权访问此通知', 'danger')
        return redirect(url_for('profile'))
    
    # 标记通知为已读
    notification.is_read = True
    db.session.commit()
    
    # 根据通知类型进行跳转
    if notification.type == 'post_pending' and current_user.is_admin:
        # 管理员收到的待审核帖子通知
        return redirect(url_for('admin_posts', status='pending'))
    elif notification.type in ['post_approved', 'post_rejected'] and notification.related_id:
        # 帖子审核结果通知
        return redirect(url_for('view_post', post_id=notification.related_id))
    elif notification.type in ['book_approved', 'book_rejected'] and notification.related_id:
        # 图书审核结果通知
        return redirect(url_for('view_book', book_id=notification.related_id))
    elif notification.type == 'invite' and notification.related_id:
        # 圈子邀请通知
        return redirect(url_for('profile') + '#notifications')
    else:
        # 其他通知
        return redirect(url_for('profile'))

@app.route('/favorite-books')
@login_required
def favorite_books():
    """显示用户收藏的图书"""
    favorite_books = current_user.favorite_books
    # 记录用户活动
    record_activity(current_user.id, 'view_favorite_books')
    return render_template('favorite_books.html', favorite_books=favorite_books)

@app.route('/post/<int:post_id>/favorite', methods=['POST'])
@login_required
def toggle_favorite_post(post_id):
    try:
        post = Post.query.get_or_404(post_id)
        
        # 查询用户是否已收藏
        is_favorited = db.session.query(post_favorites).filter_by(
            user_id=current_user.id, post_id=post_id
        ).first() is not None
        
        if is_favorited:
            # 已收藏，取消收藏
            db.session.execute(post_favorites.delete().where(
                and_(post_favorites.c.user_id == current_user.id, post_favorites.c.post_id == post_id)
            ))
            post.favorites -= 1
            action = 'unfavorited'
        else:
            # 未收藏，添加收藏
            db.session.execute(post_favorites.insert().values(
                user_id=current_user.id, post_id=post_id
            ))
            post.favorites += 1
            action = 'favorited'
            
            # 添加通知
            if post.author_id != current_user.id:
                notification = Notification(
                    user_id=post.author_id,
                    content=f'{current_user.username} 收藏了您的帖子《{post.title}》',
                    type='post_favorite',
                    related_id=post_id
                )
                db.session.add(notification)
        
        db.session.commit()
        record_activity(current_user.id, f'post_{action}', post_id)
        
        return jsonify({'success': True, 'action': action, 'favorites': post.favorites})
    except Exception as e:
        db.session.rollback()
        print(f"Error in toggle_favorite_post: {e}")
        return jsonify({'success': False, 'message': '操作失败，请稍后重试', 'error': str(e)}), 500

@app.route('/favorite-posts')
@login_required
def favorite_posts():
    try:
        page = request.args.get('page', 1, type=int)
        
        # 获取用户收藏的帖子
        posts = Post.query.join(post_favorites).filter(
            post_favorites.c.user_id == current_user.id,
            Post.status == 'approved'
        ).order_by(Post.created_at.desc()).paginate(page=page, per_page=10)
        
        # 获取当前用户已点赞的帖子ID列表
        liked_posts = db.session.query(post_likes.c.post_id).filter_by(user_id=current_user.id).all()
        liked_post_ids = {post_id for (post_id,) in liked_posts}
        
        record_activity(current_user.id, 'view_favorite_posts')
        return render_template('favorite_posts.html', posts=posts, liked_post_ids=liked_post_ids)
    except Exception as e:
        print(f"Error in favorite_posts: {e}")
        flash('获取收藏帖子时出错，请稍后再试', 'danger')
        return redirect(url_for('forum'))

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
        # 确保默认头像存在
        ensure_default_avatar()
        # 确保默认封面存在
        ensure_default_cover()
    app.run(debug=True) 